//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Query Application Block
//===============================================================================

using System;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using EntLibContrib.Query.Instrumentation;
using EntLibContrib.Query.Properties;

namespace EntLibContrib.Query.Queries
{
	/// <summary>
	/// Abstract Query.
	/// </summary>
	/// <remarks>
	/// This provider is the base for all queries
	/// </remarks>
	public abstract class QueryBase : IQuery
	{
		#region Constants
		private QueryType queryType = QueryType.Custom;
		#endregion

		#region Fields
		private string name;
		private ICustomCommand customCommand;
		private IParameterSet parameterSet;
		private QueryInstrumentationProvider instrumentationProvider;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the query name.
		/// </summary>
		/// <value>The name.</value>
		public string Name
		{
			get { return name; }
		}

		/// <summary>
		/// Gets the type of the query.
		/// </summary>
		/// <value>The type of the query.</value>
		public virtual QueryType QueryType
		{
			get { return queryType; }
		}

		/// <summary>
		/// Gets the custom command.
		/// </summary>
		/// <value>The custom command.</value>
		public ICustomCommand CustomCommand
		{
			get { return customCommand; }
		}

		/// <summary>
		/// Gets the query parameter set.
		/// </summary>
		/// <value>A set of parameters.</value>
		public IParameterSet ParameterSet
		{
			get { return parameterSet; }
		}

		/// <summary>
		/// Gets or sets the instrumentation provider.
		/// </summary>
		/// <value>The instrumentation provider.</value>
		public QueryInstrumentationProvider InstrumentationProvider
		{
			get { return instrumentationProvider; }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new default instance of the <see cref="QueryBase"/> class.
		/// </summary>
		/// <param name="name">The query name.</param>
		/// <param name="customCommand">The custom command.</param>
		/// <param name="parameterSet">The parameter set.</param>
		/// <param name="instrumentationProvider">The instrumentation provider.</param>
		protected QueryBase(string name,
												ICustomCommand customCommand,
												IParameterSet parameterSet,
												QueryInstrumentationProvider instrumentationProvider)
		{
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");

			this.name = name;
			this.customCommand = customCommand;
			if (parameterSet == null)
				this.parameterSet = new ParameterSet();
			else
				this.parameterSet = parameterSet;
			this.instrumentationProvider = instrumentationProvider;
		}
		#endregion

		#region Public Members
		/// <summary>
		/// Executes a read command and returns the results.
		/// </summary>
		/// <returns>
		/// Dataset containing the results of the read operation
		/// </returns>
		/// <remarks>
		/// 	<para>this method requires the parameter values to be set through the properties.</para>
		/// 	<para>Output parameters will update the parameter property values.</para>
		/// </remarks>
		public virtual DataSet ExecuteForRead()
		{
			return ExecuteForRead(new Dictionary<string, object>());
		}

		/// <summary>
		/// Executes a read command and returns the results.
		/// </summary>
		/// <param name="parameterValues">The parameter values.</param>
		/// <returns>
		/// Dataset containing the results of the read operation
		/// </returns>
		/// <remarks>
		/// 	<para>This method takes the parameter values from the given dictionary.</para>
		/// 	<para>Output parameters will update the parameter property values.</para>
		/// </remarks>
		public abstract DataSet ExecuteForRead(IDictionary<string, object> parameterValues);

		/// <summary>
		/// Executes a write command and saves data to the data source.
		/// </summary>
		/// <remarks>
		/// 	<para>this method requires the parameter values to be set through the properties.</para>
		/// 	<para>Output parameters will update the parameter property values.</para>
		/// </remarks>
		public virtual void ExecuteForWrite()
		{
			ExecuteForWrite(new Dictionary<string, object>());
		}

		/// <summary>
		/// Executes a write command and saves data to the data source.
		/// </summary>
		/// <param name="parameterValues">The parameter values.</param>
		/// <remarks>
		/// 	<para>This method takes the parameter values from the given dictionary.</para>
		/// 	<para>Output parameters will update the parameter property values.</para>
		/// </remarks>
		public abstract void ExecuteForWrite(IDictionary<string, object> parameterValues);

		/// <summary>
		/// Executes a write command and saves data to the data source.
		/// </summary>
		/// <param name="parameterValues">The parameter values as a DataSet.</param>
		/// <remarks>
		/// 	<para>This method takes the parameter values from the given dataset.</para>
		/// 	<para>The dataset column names must match the parameter names.</para>
		/// 	<para>Only the first Table in the dataset is considered.</para>
		/// 	<para>Multiple rows in the dataset will result in multiple writes to the data source.</para>
		/// 	<para>Output parameters will update the parameter property values but this only makes sense for single row datasets.</para>
		/// </remarks>
		public virtual void ExecuteForWrite(DataSet parameterValues)
		{
			if (parameterValues == null)
				throw new ArgumentNullException("parameterValues");

			if (parameterValues.Tables.Count > 0)
			{
				foreach (DataRow row in parameterValues.Tables[0].Rows)
				{
					IDictionary<string, object> rowParameterValues = new Dictionary<string, object>();
					foreach (DataColumn column in parameterValues.Tables[0].Columns)
					{
						rowParameterValues.Add(column.ColumnName, row[column]);
					}
					ExecuteForWrite(rowParameterValues);
				}
			}
		}
		#endregion

		#region Protected Members
		/// <summary>
		/// Sets the parameter values from a given dictionary of parameter values.
		/// </summary>
		/// <param name="parameterValues">The parameter values.</param>
		/// <remarks>the dictionary keys must match the parameter names</remarks>
		protected virtual void SetParameterValues(IDictionary<string, object> parameterValues)
		{
			if (parameterValues.Count > 0)
			{
				foreach (KeyValuePair<string, object> parameterValue in parameterValues)
				{
					foreach (KeyValuePair<string, IParameter> parameter in parameterSet.Parameters)
					{
						if (parameter.Key.ToLower(CultureInfo.CurrentCulture) == parameterValue.Key.ToLower(CultureInfo.CurrentCulture))
						{
							parameter.Value.Value = parameterValue.Value;
							break;
						}
					}
				}
			}
		}

		/// <summary>
		/// Updates the read counters.
		/// </summary>
		/// <param name="readSet">The read set.</param>
		protected virtual void UpdateReadCounters(DataSet readSet)
		{
			if (instrumentationProvider != null)
			{
				if (readSet == null)
					instrumentationProvider.FireQueryRead(QueryType, 0);
				else if (readSet.Tables.Count == 0)
					instrumentationProvider.FireQueryRead(QueryType, 0);
				else
					instrumentationProvider.FireQueryRead(QueryType, readSet.Tables[0].Rows.Count);
			}
		}

		/// <summary>
		/// Updates the write counters.
		/// </summary>
		protected virtual void UpdateWriteCounters()
		{
			if (instrumentationProvider != null)
				instrumentationProvider.FireQueryWrite(QueryType);
		}

		/// <summary>
		/// Log a read error.
		/// </summary>
		protected virtual void LogReadError(Exception exception)
		{
			// log the error
			if (instrumentationProvider != null)
				instrumentationProvider.FireQueryFailed(InternalResources.ErrorQueryReadFailedMessage, exception);
		}

		/// <summary>
		/// Log a write error.
		/// </summary>
		protected virtual void LogWriteError(Exception exception)
		{
			// log the error
			if (instrumentationProvider != null)
				instrumentationProvider.FireQueryFailed(InternalResources.ErrorQueryWriteFailedMessage, exception);
		}

		/// <summary>
		/// Builds the query command.
		/// </summary>
		/// <returns>An <see cref="ICommand"/> object</returns>
		protected abstract ICommand BuildCommand();
		#endregion
	}
}
